package com.imooc.pan.server.modules.file.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.imooc.pan.core.exception.RPanBusinessException;
import com.imooc.pan.core.utils.FileUtil;
import com.imooc.pan.core.utils.IdUtil;
import com.imooc.pan.server.common.event.log.ErrorLogEvent;
import com.imooc.pan.server.modules.file.context.CreateFolderContext;
import com.imooc.pan.server.modules.file.context.FileChunkMergeAndSaveContext;
import com.imooc.pan.server.modules.file.context.FileSaveContext;
import com.imooc.pan.server.modules.file.context.FileUploadContext;
import com.imooc.pan.server.modules.file.converter.FileConverter;
import com.imooc.pan.server.modules.file.entity.RPanFile;
import com.imooc.pan.server.modules.file.entity.RPanFileChunk;
import com.imooc.pan.server.modules.file.entity.RPanUserFile;
import com.imooc.pan.server.modules.file.service.RPanFileChunkService;
import com.imooc.pan.server.modules.file.service.RPanFileService;
import com.imooc.pan.server.modules.file.mapper.RPanFileMapper;
import com.imooc.pan.storage.engine.core.StorageEngine;
import com.imooc.pan.storage.engine.core.context.DeleteFileContext;
import com.imooc.pan.storage.engine.core.context.MergeFileContext;
import com.imooc.pan.storage.engine.core.context.StoreFileContext;
import org.assertj.core.util.Lists;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
* @author 黄怀镔
* @description 针对表【r_pan_file(物理文件信息表)】的数据库操作Service实现
* @createDate 2024-06-06 22:44:17
*/
@Service
public class RPanFileServiceImpl extends ServiceImpl<RPanFileMapper, RPanFile> implements RPanFileService, ApplicationContextAware {


    @Autowired
    private StorageEngine storageEngine;

    private ApplicationContext applicationContext;

    @Autowired
    private RPanFileChunkService rPanFileChunkService;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 上传文件
     * 保存上传的文件信息
     * @param fileContext
     */
    @Override
    public void saveFile(FileSaveContext fileContext) {
        storeMultipartFile(fileContext);
        RPanFile rPanFile = doSaveFile(fileContext.getFilename(),
                fileContext.getIdentifier(),
                fileContext.getRealPath(),
                fileContext.getUserId(),
                fileContext.getTotalSize());
        fileContext.setRecord(rPanFile);
    }


    /**
     * 文件物理合并
     * 保存文件实习信息
     * @param context
     */
    @Override
    public void mergeFileChunkAndSaveFile(FileChunkMergeAndSaveContext context) {
        //文件合并操作
        doMergeFileChunk(context);
        //保存文件实体信息
        RPanFile record = doSaveFile(context.getFilename(),
                context.getIdentifier(),
                context.getRealPath(),
                context.getUserId(),
                context.getTotalSize()
                );
        context.setRecord(record);
    }

    /**
     * 文件合并操作
     * 查询文件分片记录
     * 根据文件分片记录去合并文件
     * 删除文件分片记录
     * 封装合并文件的真实存储路径到上下文信息中
     * @param context
     */
    private void doMergeFileChunk(FileChunkMergeAndSaveContext context) {
        LambdaQueryWrapper<RPanFileChunk> lqw = new LambdaQueryWrapper<>();
        lqw.eq(RPanFileChunk::getCreateUser,context.getUserId());
        lqw.eq(RPanFileChunk::getIdentifier,context.getIdentifier());
        //过期时间大于或等于当前时间
        lqw.ge(RPanFileChunk::getExpirationTime,new Date());
        List<RPanFileChunk> list = rPanFileChunkService.list(lqw);
        if(CollectionUtils.isEmpty(list)){
            throw new RPanBusinessException("找不到对应的文件分片记录");
        }
        //获取实际存储的路径集合
        List<String> realPathList = list.stream()
                .sorted(Comparator.comparing(RPanFileChunk::getChunkNumber))
                .map(RPanFileChunk::getRealPath)
                .collect(Collectors.toList());
        //委托存储引擎合并文件
        try{
            MergeFileContext mergeFileContext = new MergeFileContext();
            mergeFileContext.setFilename(context.getFilename());
            mergeFileContext.setIdentifier(context.getIdentifier());
            mergeFileContext.setUserId(context.getUserId());
            mergeFileContext.setRealPathList(realPathList);
            storageEngine.mergeFile(mergeFileContext);
            context.setRealPath(mergeFileContext.getRealPath());
        }catch (IOException e){
            e.printStackTrace();
            throw new RPanBusinessException("文件合并失败");
        }
        //删除文件分片记录
        List<Long> fileChunkIds = list.stream().map(RPanFileChunk::getId).collect(Collectors.toList());
        if(!rPanFileChunkService.removeByIds(fileChunkIds)){
            throw new RPanBusinessException("删除文件分片记录失败");
        }
    }

    /**
     * 上传文件到存储引擎
     * 该方法委托存储引擎实现
     * @param fileContext
     */
    private void storeMultipartFile(FileSaveContext fileContext) {
        try{
            StoreFileContext context = new StoreFileContext();
            context.setFilename(fileContext.getFilename());
            context.setInputStream(fileContext.getFile().getInputStream());
            context.setTotalSize(fileContext.getTotalSize());
            storageEngine.store(context);
            fileContext.setRealPath(context.getRealPath());
        }catch (Exception e){
            e.printStackTrace();
            throw new RPanBusinessException("文件上传失败");
        }
    }

    /**
     * 保存文件操作
     * @param filename
     * @param identifier
     * @param realPath
     * @param userId
     * @param totalSize
     */
    private RPanFile doSaveFile(String filename,
                                String identifier,
                                String realPath,
                                Long userId,
                                Long totalSize) {
        RPanFile rPanFile = assembleFile(filename,identifier,realPath,userId,totalSize);
        if(!save(rPanFile)){
            try{
                DeleteFileContext deleteFileContext = new DeleteFileContext();
                deleteFileContext.setRealPathList(Lists.newArrayList(realPath));
                storageEngine.delete(deleteFileContext);
            }catch (Exception e){
                e.printStackTrace();
                //错误日志监听器
                ErrorLogEvent errorLogEvent = new ErrorLogEvent(this,"文件删除失败，请手动删除，文件路径：" + realPath,userId);
                applicationContext.publishEvent(errorLogEvent);
            }
            throw new RPanBusinessException("保存上传文件信息失败");
        }
        return rPanFile;
    }

    /**
     * 整理文件信息
     * @param filename
     * @param identifier
     * @param realPath
     * @param userId
     * @param totalSize
     * @return
     */
    private RPanFile assembleFile(String filename, String identifier, String realPath, Long userId, Long totalSize) {
        RPanFile record = new RPanFile();
        record.setFileId(IdUtil.get());
        record.setFilename(filename);
        record.setIdentifier(identifier);
        record.setRealPath(realPath);
        record.setCreateUser(userId);
        record.setFileSize(String.valueOf(totalSize));
        record.setFileSizeDesc(FileUtil.byteCountToDisplayFile(totalSize));
        record.setCreateTime(new Date());
        record.setFileSuffix(FileUtil.getFileSuffix(filename));
        return record;
    }
}
